The PyMCDP user manual

1 - A quick tour

1.1 - Describing Monotone Co-Design Problems (MCDPs)

The simplest MCDP can be defined as:

mcdp {

}

That is an empty MCDP - it has no functionality or resources.

The interface of an MCDP is defined using the keywords provides and requires:

mcdp {
    provides capacity [J]
    requires mass [g]

    # ...
}

The code above defines an MCDP with one functionality, capacity, measured in joules, and one resource, mass, measured in grams. (See how to describe types.)

Graphically, this is how the interface is represented:

G node1 node3 mass [g] node1->node3 node2 capacity [J] node2->node1

1.1.A - Constant functionality and resources

The following is a minimal example of a complete MCDP. We have given hard bounds to both capacity and mass.

mcdp {
    provides capacity [J]
    requires mass [g]

    provided capacity <= 500 J
    required mass >= 100g
}
G cluster1 node2 100 g node5 mass [g] node2->node5 node3 500 J node4 capacity [J] node4->node3

1.1.B - Describing relations between functionality and resources

Functionality and resources can depend on each other using any monotone relations.

For example, we can describe a linear relation between mass and capacity, given by the specific energy.

mcdp {
    provides capacity [J]
    requires mass [g]

    specific_energy = 4 J / g
    required mass >= provided capacity / 
                     specific_energy
}
G cluster1 node2 × 0.25000 g/J node4 mass [g] node2->node4 node3 capacity [J] node3->node2

1.1.C - Units

PyMCDP is picky about units, but generally very helpful. As long as the units have the right dimensionality, it will insert the appropriate conversions.

For example, this is the same example with the specific energy given in kWh/kg.

mcdp {
    provides capacity [J]
    requires mass [g]

    specific_energy = 200 kWh / kg
    required mass >= provided capacity / 
                     specific_energy
}
G cluster1 node2 × 0.00500 kg/kWh node4 node2->node4 [J*kg/kWh] node3 node6 mass [g] node3->node6 node4->node3 [J*kg/kWh] node5 capacity [J] node5->node2

1.2 - Composing MCDPs

Suppose we define a simple model called Battery as follows:

Battery.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    specific_energy = 100 kWh / kg
    required mass >= provided capacity / 
                     specific_energy
}
G node1 Battery node3 mass [g] node1->node3 node2 capacity [J] node2->node1

Let’s also define the MCDP Actuation1:

Actuation1.mcdpmcdp {
    provides lift [N]
    requires power [W]

    l = provided lift
    p0 = 5 W
    p1 = 6 W/N
    p2 = 7 W/N^2
    required power >= p0 + p1 * l + p2 * l^2
}
G node1 Actuation1 node3 power [W] node1->node3 node2 lift [N] node2->node1
G cluster1 node2 × 6.00000 W/N node8 node2->node8 [W] node3 node13 node3->node13 [W] node4 node11 node4->node11 [N] node12 node4->node12 [N] node5 × 7.00000 W/N² node10 node5->node10 [W] node6 + 5 W node15 power [W] node6->node15 node7 ^ 2 node9 node7->node9 [N²] node8->node3 [W] node9->node5 [N²] node10->node3 [W] node11->node2 [N] node12->node7 [N] node13->node6 [W] node14 lift [N] node14->node4

Then we can combine these two together.

We can re-use previously defined MCDPs using the syntax instance `Name. The backtick means “load symbols from the library”.

The following creates two sub-design problems, for now unconnected.

mcdp {
    actuation = instance `Actuation1
    battery = instance `Battery
}
G cluster1 node2 battery node7 node2->node7 mass [g] node3 actuation node6 node3->node6 power [W] node4 node4->node3 lift [N] node5 node5->node2 capacity [J]

To create a complete MCDP, take “endurance” as a high-level functionality. Then the energy required is equal to endurance × power.

mcdp {
    actuation = instance `Actuation1
    battery = instance `Battery

    # battery must provide power for actuation
    provides endurance [s]
    energy = provided endurance * 
        (power required by actuation)

    capacity provided by battery >= energy
}
G cluster1 node2 battery node8 node2->node8 mass [g] node3 actuation node5 node3->node5 power [W] node4 node6 node4->node6 [J] node5->node4 [W] node6->node2 capacity [J] node7 node7->node3 lift [N] node9 endurance [s] node9->node4

We can create a model with a loop by introducing another constraint.

Take extra_payload to represent the user payload that we must carry.

Then the lift provided by the actuator must be at least the mass of the battery plus the mass of the payload times gravity:

Composition.mcdpmcdp {
    actuation = instance `Actuation1
    battery = instance `Battery

    # battery must provide power for actuation
    provides endurance [s]
    energy = provided endurance * 
        (power required by actuation)

    capacity provided by battery >= energy

    # actuation must carry payload + battery
    provides payload [g]
    gravity = 9.81 m/s^2
    total_mass = (mass required by battery 
                         + provided payload)

    weight = total_mass * gravity
    lift provided by actuation >= weight

    # minimize total mass
    requires mass [g]
    required mass >= total_mass
}
G cluster1 node16 endurance [s] node6 node16->node6 node2 × 9.81000 m/s² node11 node2->node11 [g*m/s²] node3 node15 node3->node15 [g] node4 battery node14 node4->node14 mass [g] node5 actuation node12 node5->node12 power [W] node13 node6->node13 [J] node7 node10 node7->node10 [N] node8 node9 node8->node9 [g] node18 mass [g] node8->node18 node9->node2 [g] node10->node5 lift [N] node11->node7 [g*m/s²] node12->node6 [W] node13->node4 capacity [J] node14->node3 [g] node15->node8 [g] node17 payload [g] node17->node3

1.3 - Catalogues

We can also enumerate an arbitrary relation, as follows:

catalogue {
    provides capacity [J]
    requires mass [g]

    model1 |  5 MJ | 100 g
    model2 |  6 MJ | 200 g
    model3 | 10 MJ | 400 g
}
G node1 node3 mass [g] node1->node3 node2 capacity [J] node2->node1

1.4 - Coproducts (alternatives)

The coproduct construct allows to describe the idea of “alternatives”. The name comes from the category-theoretical concept of coproduct.

As an example, let us consider how to model the choice between different battery technologies.

Let us consider the model of a battery in which we take the functionality to be the capacity and the resources to be the mass [g] and the cost [$].

G node1 Battery1 node3 cost [USD] node1->node3 node4 mass [g] node1->node4 node2 capacity [J] node2->node1

Consider two different battery technologies, characterized by their specific energy (Joules per gram) and specific cost (USD per gram).

Specifically, consider Nickel-Hidrogen batteries and Lithium-Polymer batteries. On technology is cheaper but leads to heavier batteries and viceversa. Because of this fact, there might be designs in which we prefer either.

First we model the two battery technologies separately as two MCDP using the same interface (same resources and same functionality).

Battery_LiPo.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    requires cost [$]

    specific_energy = 150 Wh/kg
    specific_cost = 2.50 Wh/$

    required mass >= provided capacity / specific_energy
    required cost >= provided capacity / specific_cost
}
Battery1_NiH2.mcdpmcdp {
    provides capacity [J]
    requires mass [g]
    requires cost [$]

    specific_energy = 45 Wh/kg
    specific_cost = 10.50 Wh/$

    required mass >= provided capacity / specific_energy
    required cost >= provided capacity / specific_cost
}
G node1 Battery1_LiPo node3 cost [USD] node1->node3 node4 mass [g] node1->node4 node2 capacity [J] node2->node1 G node1 Battery1_NiH2 node3 cost [USD] node1->node3 node4 mass [g] node1->node4 node2 capacity [J] node2->node1

Then we can define the coproduct of the two using the keyword choose. Graphically, the choice is indicated through dashed lines.

Batteries.mcdpchoose(
    NiH2: `Battery1_LiPo,
    LiPo: `Battery1_NiH2
)
G cluster1 node8 cost [USD] node2 node9 mass [g] node2->node9 node3 node3->node8 node4 node5 NiH2 node4->node5 node6 LiPo node4->node6 node5->node2 node5->node3 node6->node2 node6->node3 node7 capacity [J] node7->node4

1.5 - Describing uncertain MCDPs

The keyword Uncertain is used to define uncertain relations.

For example, suppose there is some uncertain in the value of the specific energy, varying between 100 Wh/kg and 120 Wh/kg. This is one way to describe such uncertainty:

mcdp {
  provides capacity [Wh]
  requires mass     [kg]

  required mass >= 
    Uncertain(provided capacity/120 Wh/kg ,
              provided capacity/100 Wh/kg )

}

The resulting MCDP has an uncertainty gate, marked with “?”, which joins two branches, the optimistic and the pessimistic branch.

G cluster1 node2 × 0.00833 kg/Wh node6 node2->node6 [kg] node3 node8 node3->node8 [Wh] node9 node3->node9 [Wh] node4 × 0.01000 kg/Wh node7 node4->node7 [kg] node5 node11 mass [kg] node5->node11 node6->node5 [kg] node7->node5 [kg] node8->node2 [Wh] node9->node4 [Wh] node10 capacity [Wh] node10->node3

2 - Example domains

2.1 - Modeling energetics constraints in a UAV

2.1.A - Battery model

A battery is specified as a DP with the functionalities:

and resources

G node1 node4 cost [USD] node1->node4 node5 mass [g] node1->node5 node6 maintenance node1->node6 node2 capacity [J] node2->node1 node3 missions node3->node1

The parameters for the model are the specific energy, the specific cost, and the number of cycles:

specific_energy = 150 Wh/kg
specific_cost = 2.50 Wh/$
cycles = 600 []

The cost for one battery is given by

unit_cost = provided capacity / specific_cost

The number of replacements is:

num_replacements = ceil(provided missions / cycles)

The total budget for the solution is given by the unit cost times the number of replacements:

required cost >= unit_cost  * num_replacements

The code below is the complete model for the battery:

Battery_LiPo1.mcdpmcdp {
    provides capacity [J]
    provides missions [R]

    requires mass     [g]
    requires cost     [$]

    # Number of replacements
    requires maintenance [R]

    # Battery properties
    specific_energy = 150 Wh/kg
    specific_cost = 2.50 Wh/$
    cycles = 600 []

    # How many times should it be replaced?
    num_replacements = ceil(provided missions / cycles)
    required maintenance >= num_replacements

    required mass >= provided capacity / specific_energy

    # cost for one battery
    unit_cost = provided capacity / specific_cost
    required cost >= unit_cost  * num_replacements
}

This is a graphical representation of the network of constraints:

G cluster1 node2 × 0.00167 node11 node2->node11 node3 × 0.40000 USD/Wh node13 node3->node13 [J*USD/Wh] node4 × 0.00667 kg/Wh node12 node4->node12 [J*kg/Wh] node5 node14 node5->node14 [J*USD/Wh] node6 ceil node16 node6->node16 node7 node22 mass [g] node7->node22 node8 node17 node8->node17 [J] node18 node8->node18 [J] node9 node15 node9->node15 node23 maintenance node9->node23 node10 node21 cost [USD] node10->node21 node11->node6 node12->node7 [J*kg/Wh] node13->node5 [J*USD/Wh] node14->node10 [J*USD/Wh] node15->node5 node16->node9 node17->node4 [J] node18->node3 [J] node19 capacity [J] node19->node8 node20 missions node20->node2

2.1.B - Actuation

The actuation is defined as a DP where the functionalities are:

and the resources are:

G node1 node4 actuator_mass [g] node1->node4 node5 cost [USD] node1->node5 node6 power [W] node1->node6 node2 velocity [m/s] node2->node1 node3 lift [N] node3->node1

The model first describes some hard constraints for the quantities:

provided lift <= 100N
required actuator_mass >= 100 g
required cost >= 100 $
provided velocity <= 3 m/s

Then it describes a nonlinear polynomial (and monotone) relation between lift and power:

p0 = 2 W
p1 = 1.5 W/N^2

required power >= p0 + (lift^2) * p1

This is the complete MCDP:

Actuation.mcdpmcdp {
    provides lift  [N]
    provides velocity [m/s]

    requires power [W]
    requires actuator_mass [g]
    requires cost [$]

    provided lift <= 100N
    required actuator_mass >= 100 g
    required cost >= 100 $
    provided velocity <= 3 m/s

    p0 = 2 W
    p1 = 1.5 W/N^2

    required power >= p0 + (lift^2) * p1
}

This is the graphical representation:

G cluster1 node2 × 1.50000 W/N² node11 node2->node11 [W] node3 100 g node16 actuator_mass [g] node3->node16 node4 100 USD node17 cost [USD] node4->node17 node5 + 2 W node18 power [W] node5->node18 node6 node12 node6->node12 [N] node13 node6->node13 [N] node7 100 N node8 3 m/s node9 ^ 2 node10 node9->node10 [N²] node10->node2 [N²] node11->node5 [W] node12->node7 [N] node13->node9 [N] node14 velocity [m/s] node14->node8 node15 lift [N] node15->node6

2.1.C - Actuation + energetics

Next, we will define an MCDP that contains both actuation and energetics as sub-MCDPs.

We will take as high-level functionality:

G node1 node7 total_cost [USD] node1->node7 node8 total_mass [g] node1->node8 node2 velocity [m/s] node2->node1 node3 endurance [s] node3->node1 node4 extra_payload [kg] node4->node1 node5 num_missions node5->node1 node6 extra_power [W] node6->node1

In the model below, first we instantiate the models of battery and actuation:

battery = new Battery_LCO
actuation = new Actuation

The power constraint can be written as follows:

total_power = power required by actuation + extra_power
capacity provided by battery >= endurance * total_power

The lift constraint is the following:

total_mass = (
     mass required by battery +
     actuator_mass required by actuation
     + extra_payload)

gravity = 9.81 m/s^2
weight = total_mass * gravity

lift provided by actuation >= weight

The cost constraint is the following:

labor_cost = (10 $) * (maintenance required by battery)

 total_cost >= (
   cost required by actuation +
   cost required by battery +
   labor_cost)
ActuationEnergetics.mcdpmcdp {
    provides endurance     [s]
    provides extra_payload [kg]
    provides extra_power   [W]
    provides num_missions [R]
    provides velocity [m/s]

    requires total_cost [$]

    battery = new Battery_LCO
    actuation = new Actuation

    total_power = power required by actuation + extra_power
    capacity provided by battery >= endurance * total_power

    total_mass = (
      mass required by battery +
      actuator_mass required by actuation
      + extra_payload)

    gravity = 9.81 m/s^2
    weight = total_mass * gravity

    lift provided by actuation >= weight
    velocity provided by actuation >= velocity

    labor_cost = (10 $) * (maintenance required by battery)

    total_cost >= (
    cost required by actuation +
    cost required by battery +
    labor_cost)

    battery.missions >= num_missions

    requires total_mass >= total_mass   
}
G cluster1 node32 num_missions node8 battery node32->node8 node2 × 9.81000 m/s² node15 node2->node15 [g*m/s²] node3 node24 node3->node24 [USD] node4 × 10.00000 USD node26 node4->node26 [USD] node5 node22 node5->node22 [W] node6 node28 node6->node28 [g] node7 node19 node7->node19 [g] node14 node8->node14 mass [g] node25 node8->node25 cost [USD] node27 node8->node27 maintenance node9 actuation node16 node9->node16 power [W] node17 node9->node17 cost [USD] node20 node9->node20 actuator_mass [g] node10 node18 node10->node18 [g] node35 total_mass [g] node10->node35 node11 node21 node11->node21 [N] node12 node23 node12->node23 [J] node13 node34 total_cost [USD] node13->node34 node14->node6 [g] node15->node11 [g*m/s²] node16->node5 [W] node17->node13 [USD] node18->node2 [g] node19->node6 [g] node20->node7 [g] node21->node9 lift [N] node22->node12 [W] node23->node8 capacity [J] node24->node13 [USD] node25->node3 [USD] node26->node3 [USD] node27->node4 node28->node10 [g] node33 extra_power [W] node33->node5 node29 velocity [m/s] node29->node9 node30 endurance [s] node30->node12 node31 extra_payload [kg] node31->node7

2.1.D - Other parts

These are other parts that we need.

G node1 node3 power [W] node1->node3 node2 computation [flops] node2->node1 G node1 node5 power [W] node1->node5 node2 resolution [pixels/deg] node2->node1 node3 fov [deg] node3->node1 node4 framerate [Hz] node4->node1
G node1 node3 camera_fov [deg] node1->node3 node4 camera_framerate [Hz] node1->node4 node5 computation [flops] node1->node5 node6 camera_resolution [pixels/deg] node1->node6 node2 velocity [m/s] node2->node1 G node1 node3 velocity [m/s] node1->node3 node4 endurance [s] node1->node4 node2 distance [km] node2->node1
Computer.mcdpmcdp {
    requires power [W]
    provides computation [flops]

    p0 = 1 W
    p1 = 100 W/flops
    power >= p0 + p1 * computation
}
Sensor.mcdptemplate mcdp {
    provides framerate [Hz]
    provides resolution [pixels/deg]
    provides fov [deg]
    requires power [W]
}
Perception.mcdptemplate mcdp {
    provides velocity [m/s]
    requires computation [flops]
    requires camera_framerate [Hz]
    requires camera_resolution [pixels/deg]
    requires camera_fov [deg]
}
Strategy.mcdpmcdp {
    provides distance [km]
    requires endurance [s]
    requires velocity [m/s]

    distance <= endurance * velocity
}

2.1.E - Complete drone model

Based on the previous models, we can assemble the model for the entire drone, with high-level functionality: travel_distance carry payload * num_missions

G node1 node5 total_cost_ownership [USD] node1->node5 node2 travel_distance [km] node2->node1 node3 num_missions node3->node1 node4 carry_payload [g] node4->node1 G cluster1 node2 perception node13 node2->node13 camera_fov [deg] node14 node2->node14 camera_framerate [Hz] node16 node2->node16 camera_resolution [pixels/deg] node22 node2->node22 computation [flops] node3 node26 node3->node26 [W] node4 node30 total_cost_ownership [USD] node4->node30 node5 actuation_energetics node18 node5->node18 total_cost [USD] node23 node5->node23 total_mass [g] node6 shipping node25 node6->node25 postage [USD] node7 strategy node19 node7->node19 endurance [s] node20 node7->node20 velocity [m/s] node8 node15 node8->node15 [kg] node9 node17 node9->node17 [m/s] node24 node9->node24 [m/s] node10 computer node21 node10->node21 power [W] node11 sensor node12 node11->node12 power [W] node12->node3 [W] node13->node11 fov [deg] node14->node11 framerate [Hz] node15->node5 extra_payload [kg] node16->node11 resolution [pixels/deg] node17->node2 velocity [m/s] node18->node4 [USD] node19->node5 endurance [s] node20->node9 [m/s] node21->node3 [W] node22->node10 computation [flops] node23->node6 ships [g] node24->node5 velocity [m/s] node25->node4 [USD] node26->node5 extra_power [W] node27 travel_distance [km] node27->node7 node28 num_missions node28->node5 node29 carry_payload [g] node29->node8
DroneComplete.mcdpmcdp {
    provides travel_distance [km]
    provides num_missions [R]
    provides carry_payload [g]

    requires total_cost_ownership [$]

    strategy = new Strategy

    actuation_energetics = new ActuationEnergetics

    endurance provided by actuation_energetics>= endurance required by strategy
    velocity provided by actuation_energetics>= velocity required by strategy
    num_missions provided by actuation_energetics >= provided num_missions
    extra_payload provided by actuation_energetics>= provided carry_payload
    distance provided by strategy >= provided travel_distance

    computer = new Computer
    perception = new Perception
    computation provided by computer>= computation required by perception


    sensor = new Sensor
    resolution provided by sensor >= camera_resolution required by perception
    framerate provided by sensor >= camera_framerate required by perception
    fov provided by sensor >= camera_fov required by perception

    velocity provided by perception >= velocity required by strategy
    extra_power provided by actuation_energetics >= power required by computer + power required by sensor

    # We can take into account the shipping cost
    shipping = new Shipping

    total_mass = total_mass required by actuation_energetics

    ships provided by shipping >= total_mass

    total_cost_ownership >= postage required by shipping + total_cost required by actuation_energetics
}

2.1.F - Customer preferences in the loop

Finally, we can define a model of the customer:

G node1 node3 travel_distance [km] node1->node3 node4 num_missions node1->node4 node5 carry_payload [g] node1->node5 node2 budget [USD] node2->node1

and put it in the loop:

CustomerPlusEngineering.mcdpmcdp {
    customer = new Customer
    robot = new DroneComplete

    budget provided by customer >= total_cost_ownership required by robot

    travel_distance provided by robot >= travel_distance required by customer
    num_missions    provided by robot >= num_missions    required by customer
    carry_payload   provided by robot >= carry_payload   required by customer
}
G cluster1 node2 customer node4 node2->node4 num_missions node5 node2->node5 carry_payload [g] node7 node2->node7 travel_distance [km] node3 robot node6 node3->node6 total_cost_ownership [USD] node4->node3 num_missions node5->node3 carry_payload [g] node6->node2 budget [USD] node7->node3 travel_distance [km]

2.2 - The socket/plugs/converters domain

We are going to model the domain of AC sockets and plugs.

2.2.A - Sockets

There are many AC power plugs and sockets.

Type A
Type B
Type C
Type D
Type E
Type F
Type G
Type H
Type I
Type J
Type K
Type L
Type M
Type N
Type O

Some of them are compatible. For example we can fit a plug ot Type A into a socket of Type B. This creates a natural partial order structure.

We can use a finite_poset to describe the poset as follows:

socket_type.mcdp_posetfinite_poset {
  TypeA TypeB TypeC TypeD TypeE TypeF  TypeG  TypeH  
  TypeI2 TypeI3 TypeJ TypeK TypeL TypeM TypeN  TypeO

  # A <= B: if it fits in A, it also fits in B
  TypeA  <= TypeB
  TypeC  <= TypeD   
  TypeE  <= TypeD   
  TypeF  <= TypeD   
  TypeC  <= TypeE
  TypeF  <= TypeE
  TypeC  <= TypeH
  TypeI2 <= TypeI3
  TypeC  <= TypeJ
  TypeK  <= TypeC
  TypeC  <= TypeL
  TypeC  <= TypeN
  TypeC  <= TypeO

  None <= TypeA 
  None <= TypeB 
  None <= TypeC 
  None <= TypeD 
  None <= TypeE 
  None <= TypeF  
  None <= TypeG  
  None <= TypeH  
  None <= TypeI2 
  None <= TypeI3 
  None <= TypeJ 
  None <= TypeK 
  None <= TypeL 
  None <= TypeM 
  None <= TypeN  
  None <= TypeO
}

2.2.B - Voltages

Around the world, the two main voltages are 110V and 220V. In this case, we cannot use the usual Volt poset indicated by V, because that would mean that 220V is always preferable to 110V.

Thus we create a discrete poset as follows:

AC_voltages.mcdp_posetfinite_poset {
  v110 v220
}

2.2.C - Frequencies

Similarly, we model different frequencies with the poset

AC_frequencies.mcdp_posetfinite_poset {
    f50 f60
}

2.2.D - Power consumption

The function of a socket in the wall is to provide power. This function is parameterized (at least) by:

Therefore, we can create the poset `AC_power as follows:

AC_power.mcdp_posetproduct(
       socket: `socket_type,
      voltage: `AC_voltages,
    frequency: `AC_frequencies,
        watts: W
)

2.2.E - Modeling a plug adapter

Based on these definitions, we can define the function of a socket adapter.

Consider one of these OREI adapters, which you can buy for $7.31:

This is an adapter from Type L to either Type C or Type A:

Type L
Type A
Type C

A plug adapter can be modeled as follows:

orei.mcdp# This device converts from TypeC to either TypeL or TypeA
mcdp {
    provides out [`AC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 7.31 USD

    (required in).socket >= `socket_type : TypeL
    (required in).voltage   >=  (provided out).voltage
    (required in).frequency >=  (provided out).frequency
    (required in).watts     >=  (provided out).watts

    (provided out).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})
}
G node1 node3 budget [USD] node1->node3 node4 in [`AC_power] node1->node4 node2 out [`AC_power] node2->node1 G cluster1 node2 7.31 USD node13 budget [USD] node2->node13 node3 'TypeL' node11 node3->node11 [`socket_type] node4 ↓{'TypeA', 'TypeC'} node5 node7 node5->node7 frequency [`AC_frequencies] node8 node5->node8 socket [`socket_type] node9 node5->node9 watts [W] node10 node5->node10 voltage [`AC_voltages] node6 node14 in [`AC_power] node6->node14 node7->node6 frequency [`AC_frequencies] node8->node4 [`socket_type] node9->node6 watts [W] node10->node6 voltage [`AC_voltages] node11->node6 socket [`socket_type] node12 out [`AC_power] node12->node5

2.2.F - A 2-in-1 adapter

This is another handy 2-in-1 adapter that sells for 6.31:

This one provides 2 outputs:

orei_2in1.mcdpmcdp {
    provides out1 [`AC_power]
    provides out2 [`AC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 6.31 USD
 
    (required in).socket >= `socket_type : TypeM

    (provided out1).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})
    (provided out2).socket <= any-of({`socket_type : TypeA, `socket_type : TypeC})

    # this forces the two voltages to be the same
    (required in).voltage   >=  (provided out1).voltage
    (required in).voltage   >=  (provided out2).voltage
    (required in).frequency >=  (provided out1).frequency
    (required in).frequency >=  (provided out2).frequency
    # this says that the power sums
    total_power = (provided out1).watts + (provided out2).watts
    (required in).watts >=  total_power
}

We can forget all this complexity and consider the block:

G node1 node4 budget [USD] node1->node4 node5 in [`AC_power] node1->node5 node2 out1 [`AC_power] node2->node1 node3 out2 [`AC_power] node3->node1 G cluster1 node24 out1 [`AC_power] node10 node24->node10 node2 node13 node2->node13 [W] node3 6.31 USD node26 budget [USD] node3->node26 node4 'TypeM' node21 node4->node21 [`socket_type] node5 ↓{'TypeA', 'TypeC'} node6 node18 node6->node18 [`AC_voltages] node7 node23 node7->node23 [`AC_frequencies] node8 ↓{'TypeA', 'TypeC'} node9 node15 node9->node15 frequency [`AC_frequencies] node16 node9->node16 socket [`socket_type] node19 node9->node19 watts [W] node22 node9->node22 voltage [`AC_voltages] node12 node10->node12 voltage [`AC_voltages] node14 node10->node14 socket [`socket_type] node17 node10->node17 watts [W] node20 node10->node20 frequency [`AC_frequencies] node11 node27 in [`AC_power] node11->node27 node12->node6 [`AC_voltages] node13->node11 watts [W] node14->node8 [`socket_type] node15->node7 [`AC_frequencies] node16->node5 [`socket_type] node17->node2 [W] node18->node11 voltage [`AC_voltages] node19->node2 [W] node20->node7 [`AC_frequencies] node21->node11 socket [`socket_type] node22->node6 [`AC_voltages] node23->node11 frequency [`AC_frequencies] node25 out2 [`AC_power] node25->node9

2.2.G - DC connectors

We can repeat the same story with DC connectors.

barrel_connectors.mcdp_posetfinite_poset {
    tip_sleeve_2_5mm
    tip_sleeve_3_5mm
    barrel_5mm
    # EIAJ-01 (2.35mm barrel, 0.7mm inner diameter)
    barrel_2_35mm
    barrel_3_35mm_1_35mm
    # EIAJ-02 (4.0mm barrel, 1.7mm inner diameter)
    barrel_4mm_1_7mm
    barrel_4mm_1_5mm
    barrel_4mm_2_5mm

    # used by Youbot charger
    Neutrik_NC4MXX

    barrel_none
    barrel_none <= tip_sleeve_2_5mm
    barrel_none <= tip_sleeve_3_5mm
    barrel_none <= barrel_5mm
    barrel_none <= barrel_2_35mm
    barrel_none <= barrel_3_35mm_1_35mm
    barrel_none <= barrel_4mm_1_7mm
    barrel_none <= barrel_4mm_1_5mm
    barrel_none <= barrel_4mm_2_5mm
    barrel_none <= Neutrik_NC4MXX
}

2.2.H - USB connectors

USB_connectors.mcdp_posetfinite_poset {
    # female (socket)
    none_usb <= USB_Micro_A 
    none_usb <=    USB_Micro_B
    none_usb <=    USB_Mini_A
    none_usb <=    USB_Mini_B
    none_usb <=    USB_Std_A
    none_usb <=    USB_Std_B
    none_usb <=    USB_Type_C
    # male
    none_usb <=    USB_Micro_A_male
    none_usb <=    USB_Micro_B_male
}

2.2.I - DC connectors

We can define DC connectors to be the union (co-product) of the two sets:

DC_connectors.mcdp_posetcoproduct(`barrel_connectors, `USB_connectors)

2.2.J - DC power

DC_voltages.mcdp_posetfinite_poset {
  v1_5 v5 v6_6 

  none_voltages <= v1_5
  none_voltages <= v5
  none_voltages <= v6_6
}
DC_power.mcdp_posetproduct(
 connector: `DC_connectors,
   voltage: `DC_voltages,
      amps: A
)

2.2.K - AC-DC converters

This wall charger can be used to convert from AC power to DC power.

Ravpower.mcdpmcdp {
    provides out1 [`DC_power]
    provides out2 [`DC_power]
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 10.99 USD    

    (required in).socket >= `socket_type : TypeA

    (provided out1).voltage   <= `DC_Voltages: v5
    (provided out2).voltage   <= `DC_Voltages: v5
    (provided out1).connector <= `USB_connectors:USB_Std_A 
    (provided out2).connector <= `USB_connectors:USB_Std_A

    # this forces the two voltages to be the same
    # this says that the power sums
    amps = (provided out1).amps + (provided out2).amps
    amps <= 2.4 A
    power = 5 V * (amps)

    (required in).watts >= power
    (required in).voltage >= `AC_voltages: v110
    (required in).socket >= `socket_type: TypeA
    (required in).frequency >= `AC_frequencies: f50

}
G node1 node4 budget [USD] node1->node4 node5 in [`AC_power] node1->node5 node2 out1 [`DC_power] node2->node1 node3 out2 [`DC_power] node3->node1 G cluster1 node40 budget [USD] node2 × 5.00000 V node24 node2->node24 [W] node3 node34 node3->node34 [A] node4 10.99 USD node4->node40 node5 'v110' node35 node5->node35 [`AC_voltages] node6 'TypeA' node32 node6->node32 [`socket_type] node7 'f50' node29 node7->node29 [`AC_frequencies] node8 'TypeA' node37 node8->node37 [`socket_type] node9 node25 node9->node25 [A] node30 node9->node30 [A] node10 node21 node10->node21 [`socket_type] node11 node26 node11->node26 connector [`DC_connectors] node27 node11->node27 voltage [`DC_voltages] node28 node11->node28 amps [A] node12 node22 node12->node22 voltage [`DC_voltages] node23 node12->node23 amps [A] node31 node12->node31 connector [`DC_connectors] node13 node41 in [`AC_power] node13->node41 node14 node36 node14->node36 [`USB_connectors] node15 node33 node15->node33 [`USB_connectors] node16 'USB_Std_A' node17 2.4 A node18 'v5' node19 'v5' node20 'USB_Std_A' node21->node13 socket [`socket_type] node22->node18 [`DC_voltages] node23->node3 [A] node24->node13 watts [W] node25->node17 [A] node26->node15 [`DC_connectors] node27->node19 [`DC_voltages] node28->node3 [A] node29->node13 frequency [`AC_frequencies] node30->node2 [A] node31->node14 [`DC_connectors] node32->node10 [`socket_type] node33->node16 [`USB_connectors] node34->node9 [A] node35->node13 voltage [`AC_voltages] node36->node20 [`USB_connectors] node37->node10 [`socket_type] node39 out2 [`DC_power] node39->node11 node38 out1 [`DC_power] node38->node12

We can query the model as follows. Suppose we need 2 outputs, each of 0.5A.

solve(
      `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A,
       `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A, 
    `Ravpower)

This is the output:

↑{⟨in:⟨socket:'TypeA', voltage:'v110', frequency:'f50', watts:5 W⟩, budget:10.99 USD⟩}

The model says we have two options: we need to find an outlet of TypeM at either 110 V or 220 V which will provide 5 W of power. Moreover, we need at least 10.99 USD to buy the component.

2.2.L - Composition

This is an example of composition of the Ravpower charger and the Orei_2in1 adapter.

orei_plus_ravpower.mcdpmcdp {
    provides out [`DC_power]   
    requires in [`AC_power]   
    requires budget [USD]  
    charger = instance `Ravpower
    adapter = instance `Orei_2in1
    in required by charger <= out1 provided by adapter
    required in >= in required by adapter
    provided out <= out1 provided by charger 
    # sum the budget together
    budget >= budget required by charger + budget required by adapter
    # ignore the functions we don't need
    ignore out2 provided by charger 
    ignore out2 provided by adapter
}

Note the use of the keyword “ignore” to ignore the functionality that we do not need.

G cluster1 node2 node13 budget [USD] node2->node13 node3 ↑{⟨socket:'None', vol... node10 node3->node10 [`AC_power] node4 ↑{⟨connector:alt1:('b... node11 node4->node11 [`DC_power] node5 charger node8 node5->node8 budget [USD] node9 node5->node9 in [`AC_power] node6 adapter node7 node6->node7 budget [USD] node14 in [`AC_power] node6->node14 node7->node2 [USD] node8->node2 [USD] node9->node6 out1 [`AC_power] node10->node6 out2 [`AC_power] node11->node5 out2 [`DC_power] node12 out [`DC_power] node12->node5

We can ask now for what resources we would need for a 0.5 A load:

solve(
     `USB_connectors:USB_Std_A, `DC_voltages: v5, 0.5 A,
    `orei_plus_ravpower)

and obtain

↑{⟨in:⟨socket:'TypeM', voltage:'v110', frequency:'f50', watts:2.5 W⟩, budget:17.3 USD⟩}

2.2.M - A step-up/step-down voltage converter

Next, we are going to model a Goldsource STU-200 Step Up/Down Voltage Transformer Converter.

This is a device with 1 input and 3 outputs:

goldsource_STU_200.mcdpmcdp {
   # This provides 3 outputs: 2 AC and 1 DC
   provides out1 [`AC_power]
   provides out2 [`AC_power]
   provides out3 [`DC_power]
   requires in [`AC_power]
 
   # the AC output 1 is v110 and takes types A, B (A<=B)
   (provided out1).socket <= `socket_type: TypeB 
   (provided out1).voltage <= `AC_voltages:v110
   (provided out1).frequency <= (required in).frequency # same frequency
   power1 = (provided out1).watts

   # the AC output 2 is v220 and takes types C, D, E, F, G, H (D,G minimals)
   (provided out2).socket <= any-of({ `socket_type: TypeD, `socket_type: TypeG })
   (provided out2).voltage <= `AC_voltages:v220
   (provided out2).frequency <= (required in).frequency # same frequency
   power2 = (provided out2).watts

   # the DC output is 5v, USB Type A
   (provided out3).connector <= `USB_connectors: USB_Std_A
   (provided out3).voltage   <= `DC_voltages: v5
   amp3 = (provided out3).amps
   power3 = 5 V * (amp3)
   
   power = power1 + power2 + power3
   (required in).watts >= power

   # input is either typeC or typeD (C<=D)
   (required in).socket >= `socket_type: TypeC
   (required in).frequency >= `AC_frequencies: f50 # XXX
   (required in).voltage >= `AC_voltages: v110 # XXX
}
G node1 node5 in [`AC_power] node1->node5 node2 out1 [`AC_power] node2->node1 node3 out2 [`AC_power] node3->node1 node4 out3 [`DC_power] node4->node1 G cluster1 node40 out2 [`AC_power] node10 node40->node10 node2 × 5.00000 V node32 node2->node32 [W] node3 node20 node3->node20 [W] node4 node22 node4->node22 [W] node5 'TypeC' node23 node5->node23 [`socket_type] node6 'v110' node31 node6->node31 [`AC_voltages] node7 'f50' node38 node7->node38 [`AC_frequencies] node8 node36 node8->node36 [`AC_frequencies] node9 node26 node9->node26 amps [A] node28 node9->node28 voltage [`DC_voltages] node29 node9->node29 connector [`DC_connectors] node21 node10->node21 voltage [`AC_voltages] node24 node10->node24 socket [`socket_type] node27 node10->node27 frequency [`AC_frequencies] node33 node10->node33 watts [W] node11 node42 in [`AC_power] node11->node42 node12 ↓{'TypeD', 'TypeG'} node13 node30 node13->node30 [`USB_connectors] node14 node25 node14->node25 socket [`socket_type] node34 node14->node34 watts [W] node35 node14->node35 voltage [`AC_voltages] node37 node14->node37 frequency [`AC_frequencies] node15 'USB_Std_A' node16 'v5' node17 'TypeB' node18 'v110' node19 'v220' node20->node4 [W] node21->node19 [`AC_voltages] node22->node11 watts [W] node23->node11 socket [`socket_type] node24->node12 [`socket_type] node25->node17 [`socket_type] node26->node2 [A] node27->node8 [`AC_frequencies] node28->node16 [`DC_voltages] node29->node13 [`DC_connectors] node30->node15 [`USB_connectors] node31->node11 voltage [`AC_voltages] node32->node3 [W] node33->node3 [W] node34->node4 [W] node35->node18 [`AC_voltages] node36->node11 frequency [`AC_frequencies] node37->node8 [`AC_frequencies] node38->node8 [`AC_frequencies] node39 out1 [`AC_power] node39->node14 node41 out3 [`DC_power] node41->node9

2.2.N - A Micro USB charger

G cluster1 node24 out [`DC_power] node7 node24->node7 node2 × 5.00000 V node16 node2->node16 [W] node3 10.99 USD node25 budget [USD] node3->node25 node4 'v110' node21 node4->node21 [`AC_voltages] node5 'TypeA' node23 node5->node23 [`socket_type] node6 'f50' node22 node6->node22 [`AC_frequencies] node14 node7->node14 voltage [`DC_voltages] node17 node7->node17 amps [A] node19 node7->node19 connector [`DC_connectors] node8 node18 node8->node18 [`USB_connectors] node9 node15 node9->node15 [A] node20 node9->node20 [A] node10 node26 in [`AC_power] node10->node26 node11 'v5' node12 'USB_Micro_A_male' node13 2.4 A node14->node11 [`DC_voltages] node15->node2 [A] node16->node10 watts [W] node17->node9 [A] node18->node12 [`USB_connectors] node19->node8 [`DC_connectors] node20->node13 [A] node21->node10 voltage [`AC_voltages] node22->node10 frequency [`AC_frequencies] node23->node10 socket [`socket_type]
USBMicroCharger.mcdpmcdp {
    provides out [`DC_power] 
    requires in  [`AC_power] 
    requires budget [USD]

    budget >= 10.99 USD    

    (required in).socket >= `socket_type: TypeA
    (required in).voltage >= `AC_voltages: v110
    (required in).frequency >= `AC_frequencies: f50

    (provided out).voltage   <= `DC_voltages: v5
    (provided out).connector <= `USB_connectors: USB_Micro_A_male
     
    amps = (provided out).amps 
    amps <=  2.4 A
    power = 5 V * (amps)

    (required in).watts >= power
}

2.3 - The servo/motors domain

This is a formalization of the domain of servos/DC motors and accessories.

2.3.A - Continuous rotation

The functionality ContinuousRotation describes a device that can provided continuous rotation along an axis.

This functionality is parameterized by:

ContinuousRotation.mcdp_posetproduct( 
  torque: N*m,
  velocity: rad/s,
  duration: s,
  rigid_body: `RigidBodyID
)

2.3.B - Rotation placement

The functionality AngularPlacement describes what a servo mechanism provides: precise placement along a rotational axis.

This functionality is parameterized by:

AngularPlacement.mcdp_posetproduct (
   resolution: deg,
   torque: N*m,
   span: deg, 
   transit_time_60deg: s, # XXX: this should be "opped"
   duration: s,
   rigid_body: `RigidBodyID
)

2.3.C - PWM and PPM

The two main ways to drive motors are Pulse-Width Modulation (PWM) and Pulse-Position Modulation (PPM).

For PWM, the variables of interest are:

PWM.mcdp_posetproduct(
 voltage_max: V,
 amp_max: A,
 freq_max: Hz,
 duration: s,
 rigid_body: `RigidBodyID
)

For PPM, the variables of interest are:

PPM.mcdp_posetproduct(
 voltage_max: V,
 amp_max: A,
 freq_max: Hz,
  resolution: R, # number between 0 and 1, lower is better
 # resolution: op(R) # number between 0 and 1, lower is better
 duration: s,
 rigid_body: `RigidBodyID
)

2.3.D - Example: servo motor driven by PPM

A servo motor like the Traxxas 2075 Digital High-Torque Waterproof Servo is a device that provides the `AngularPlacement functionality, and to do so requires the `PPM resource.

G node1 Traxxas_2075 node3 budget [USD] node1->node3 node4 ppm [`PPM] node1->node4 node5 payload [`Payload] node1->node5 node2 angular_placement [`AngularPlacement] node2->node1
Traxxas_2075.mcdpmcdp {
   provides angular_placement [`AngularPlacement]
   requires ppm [`PPM]

   requires budget [USD]
   requires payload [`Payload]
   
   (required ppm).voltage_max >= 6 V
   (required ppm).amp_max >= 1 A # XXX
   (required ppm).freq_max >= 50 Hz
   (required ppm).resolution >= 0.0028   []
   (provided angular_placement).transit_time_60deg <= 0.17s   
   (provided angular_placement).torque <= 0.882 N * m
   (provided angular_placement).resolution <= 1 deg # XXX
   (provided angular_placement).span <= 360 deg # XXX
   (required ppm).duration >= (provided angular_placement).duration

   rigid_body = (provided angular_placement).rigid_body

   (required ppm).rigid_body >= rigid_body

   required budget >= 27.99 USD
   (required payload).shape >= <55mm, 20mm, 42.3mm>
   (required payload).mass >= 45 g 
   (required payload).rigid_body >= rigid_body
}

2.3.E - Example: brushless motor driven by PWM

A brushless motor like the Traxxas 3351 provides the `ContinuousRotation functionality and requires the `PWM resource.

G node1 Traxxas_3351 node3 pwm [`PWM] node1->node3 node4 budget [USD] node1->node4 node5 payload [`Payload] node1->node5 node2 cr [`ContinuousRotation] node2->node1
Traxxas_3351.mcdpmcdp {
   provides cr [`ContinuousRotation]
   requires pwm [`PWM]
   requires budget [USD]   

   requires payload [`Payload]
   
   required budget >= 99.28 USD
   (required payload).mass >= 1.6 pounds
   (required payload).shape >= <6.5in, 2in, 5.8in>
   rigid_body = (provided cr).rigid_body
   (required payload).rigid_body >= rigid_body
   (required pwm).rigid_body >= rigid_body

  (provided cr).torque <= 0 N*m
  (provided cr).velocity <= 5000 rpm

   (required pwm).freq_max >= 1600 Hz

   rating = 3500 rpm / V 
   (required pwm).voltage_max >= (provided cr).velocity / rating

   # the amps are related to the torque
   eff = 0.75 []
   (required pwm).amp_max *  (required pwm). voltage_max
       >= eff * (provided cr).torque *  (provided cr).velocity 

  (required pwm).duration >= (provided cr).duration
}

2.3.F - Example: Electronic Speed Controller

An electronic speed controller (ESC) like the VESC can drive servos/motors using PPM/PWM.

G node1 VESC node4 budget [USD] node1->node4 node5 payload [`Payload] node1->node5 node6 usb [`USBCom] node1->node6 node7 in [`PortableDCPower] node1->node7 node2 pwm [`PWM] node2->node1 node3 ppm [`PPM] node3->node1
mcdp {
    provides ppm [`PPM]
    provides pwm [`PWM]

    requires in [`PortableDCPower]
    requires usb [`USBCom]

    requires budget [USD]
    requires payload [`Payload]

    required budget >= 115 USD
    
    (required payload).shape >= <2cm, 2cm, 1cm>
    (required payload).mass  >= 10 g

    # XXX: these numbers were made up
    (provided pwm).voltage_max <= 5 V
    (provided pwm).amp_max  <= 2 A
    (provided pwm).freq_max <= 120 Hz

    # XXX: these numbers were made up
    (provided ppm).voltage_max <= 5 V
    (provided ppm).amp_max  <= 50 mA
    (provided ppm).freq_max <= 120 Hz
    (provided ppm).resolution <= 0.01 []

    endurance = max( (provided ppm).duration, (provided pwm).duration )
    (required in).duration >= endurance
    (required usb).duration >= endurance

    dc_in = (required in).dc
    (dc_in).connector >= `barrel_connectors: barrel_5mm # incorrect
    (dc_in).voltage >= `DC_voltages: v5
    (dc_in).amps >= 2 A

    (required payload).rigid_body >= (provided pwm).rigid_body
    (required payload).rigid_body >= (provided ppm).rigid_body
    (required in).rigid_body >= (provided ppm).rigid_body
    (required usb).rigid_body >= (provided ppm).rigid_body

}

2.3.G - Example: Actuation

product (
  rigid_body: `RigidBodyID,
  velocity: m/s,
  endurance: s
)
product(
  mass: kg,
  shape: m x m x m,
  rigid_body: `RigidBodyID
)

2.3.H - Example: Dagu Chassis

Dagu Chassis used for the Duckiebot.

G node1 DaguChassis node5 rb_id [`RigidBodyID] node1->node5 node6 budget [USD] node1->node6 node7 pwm2 [`PWM] node1->node7 node8 pwm1 [`PWM] node1->node8 node2 motion [`Motion] node2->node1 node3 payload_bottom [`Payload] node3->node1 node4 payload_top [`Payload] node4->node1
mcdp {
   provides motion [`Motion]
   provides payload_bottom [`Payload] 
   provides payload_top    [`Payload] 

   requires budget [USD]
   required budget >= 15 USD

   requires pwm1 [`PWM]
   requires pwm2 [`PWM]
   
   mass = (provided payload_bottom).mass + (provided payload_top).mass 
   
   shape_top = (provided payload_top).shape
   shape_bottom = (provided payload_bottom).shape
 

   shape_top <= <14cm, 11cm, 30cm>
   shape_bottom <= <14cm, 11cm, 3cm>

   (provided motion).velocity <= 35 cm/ s

   c = 0.1 A / kg
   current = max(mass * c, 0.4 A)

   endurance = (provided motion).endurance
   rigid_body = max(max((provided motion).rigid_body, (provided payload_bottom).rigid_body),
                    (provided payload_top).rigid_body)

   (required pwm1).amp_max >= current
   (required pwm1).voltage_max >= 5 V
   (required pwm1).freq_max >= 60 Hz
   (required pwm1).duration >= endurance
   (required pwm1).rigid_body >= rigid_body

   (required pwm2).amp_max >= current  
   (required pwm2).voltage_max >= 5 V
   (required pwm2).freq_max >= 60 Hz
   (required pwm2).duration >= endurance
   (required pwm2).rigid_body >= rigid_body

   requires rb_id [`RigidBodyID] 
   required rb_id >=  rigid_body
}

2.3.I - The Youbot Base

Youbot base

G node1 YoubotBase node6 rigid_body [`RigidBodyID] node1->node6 node7 budget [USD] node1->node7 node8 in [`DC_Charging] node1->node8 node2 motion [`Motion] node2->node1 node3 usbcom [`USBCom] node3->node1 node4 payload [`Payload] node4->node1 node5 out [`PortableDCPower] node5->node1
mcdp {
   provides out [`PortableDCPower]
   provides usbcom [`USBCom]
   provides motion [`Motion]
   provides payload [`Payload]

   requires budget [USD]
   
   (provided motion).velocity <= 5 m/s
   required budget >= 10000 USD

   computer = instance `YoubotBaseComputer 

   provided usbcom <= usbcom provided by computer 

   battery = instance `YoubotBaseBattery

   mass = (provided payload).mass
   shape = (provided payload).shape
 

   endurance1 = max( (provided usbcom).duration, (provided motion).endurance)
   #endurance2 = (provided out).duration
   
   endurance = endurance1 

   motors = instance mcdp {
      provides endurance [s]
      requires in [`PortableDCPower]
   }

   motors.endurance >= endurance

   in required by motors  <= out3 provided by battery

  # limit x and y
   shape <= <30cm, 50cm, 50cm>
   
   mass <= 10 kg

   out1 provided by battery >= dc required by computer
   out2 provided by battery >= provided out
 
   (provided out).dc <= (out2 provided by battery).dc
   (provided out).duration <= (out2 provided by battery).duration
   

   rigid_body1 = max( (provided out).rigid_body, (provided usbcom).rigid_body )
   rigid_body2 = max( (provided motion).rigid_body, (provided payload).rigid_body )
   rigid_body = max(rigid_body1, rigid_body2)

   rigid_body <= (out2 provided by battery).rigid_body

   requires rigid_body [`RigidBodyID]
   required rigid_body >= rigid_body
   requires in for battery
}

2.3.J - iRobot Create

iRobot Create

G node1 IRobotCreate node5 usbcom [`USBCom] node1->node5 node6 budget [USD] node1->node6 node7 in [`DC_Charging] node1->node7 node2 motion [`Motion] node2->node1 node3 dc [`PortableDCPower] node3->node1 node4 payload [`Payload] node4->node1
mcdp {
   provides dc [`PortableDCPower]
   provides motion [`Motion]
   provides payload [`Payload]

   requires budget [USD]

   requires in [`DC_Charging]

   requires usbcom [`USBCom]

   payload_mass = (provided payload).mass
   payload_shape = (provided payload).shape
   payload_shape <= < 24cm, 24cm, 40cm >

   max_mass = 5kg
   payload_mass <= max_mass

   # provides usb out
   velocity = (provided motion).velocity
   endurance = (provided motion).endurance
   
   v1 = 18.5 in/sec
   v0 = 10 in/sec
   # portion of total mass
   f = payload_mass / max_mass
   # velocity <= v1 - (v1-v0) * f 
   velocity + (v1 - v0) * f <= v1
   e1 = 2 hours
   e0 = 30 min
   # endurance <= e1 - (e1 - e0) * f
   endurance + (e1-e0) * f <= e1

   #  enforce that all rigid body IDs are the same
   rigid_body1 = (provided motion).rigid_body 
   rigid_body2 = (provided dc).rigid_body 
   rigid_body3 = (provided payload).rigid_body
   rigid_body = max(max(rigid_body1, rigid_body2), rigid_body3)
   rigid_body <= any-of(Maximals(`RigidBodyID))

   out = (provided dc).dc 
   (provided dc).duration <= 1 hour
   (out).connector <= `barrel_connectors: barrel_2_35mm # XXX
   (out).amps <= 1 A  
   (out).voltage <= `DC_voltages: v5 # XXX
   required budget >= 199.99 USD

   ignore usbcom provided by _res_usbcom # XXX
   ignore in provided by _res_in # XXX
}

2.4 - Space Rovers Energetics

2.4.A - One option: thermocouple

A thermocouple is a device that converts heat into electrical power.

Thermocouple.mcdptemplate mcdp {
  provides power [W]
  requires heat [W]
  requires mass [g]
  requires cost [USD]
}
G node1 Thermocouple node3 heat [W] node1->node3 node4 cost [USD] node1->node4 node5 mass [g] node1->node5 node2 power [W] node2->node1
One way to get the heat is to procure a bit of Plutonium.
PlutoniumPellet.mcdpmcdp {
  provides heat [W]
  requires plutonium [g]
  requires mass [g]

  # Plutonium 238
  decay_heat = 560 W/kg

  m = heat / decay_heat

  required mass >= m
  required plutonium >= m
}
G node1 PlutoniumPellet node3 mass [g] node1->node3 node4 plutonium [g] node1->node4 node2 heat [W] node2->node1
We can connect the two, by specifying that the heat required by the thermocouple is provided by the pellet:

G node1 thermocouple node3 node1->node3 heat [W] node6 node1->node6 cost [USD] node8 node1->node8 mass [g] node2 plutonium_pellet node5 node2->node5 mass [g] node7 node2->node7 plutonium [g] node3->node2 heat [W] node4 node4->node1 power [W]

The masses are summed together:
mcdp { 
    plutonium_pellet = new PlutoniumPellet
    thermocouple = new Thermocouple

    heat provided by plutonium_pellet >= heat required by thermocouple

    requires plutonium for plutonium_pellet
    provides power_profile <= thermocouple.power
    requires cost >= thermocouple.cost
    requires mass >= thermocouple.mass + plutonium_pellet.mass
}
G cluster1 node8 power_profile [W] node3 thermocouple node8->node3 node2 node10 mass [g] node2->node10 node5 node3->node5 heat [W] node7 node3->node7 mass [g] node9 cost [USD] node3->node9 node4 plutonium_pellet node6 node4->node6 mass [g] node11 plutonium [g] node4->node11 node5->node4 heat [W] node6->node2 [g] node7->node2 [g]

Putting everything together

G cluster1 cluster35 battery_plus_solar cluster36 battery_plus_solar cluster8 rtig cluster11 rtig cluster14 plutonium_pellet cluster25 solar cluster28 battery node2 node50 mass [g] node2->node50 node37 node3 node37->node3 node38 node38->node2 node39 battery node41 node39->node41 charging_profile [W] node42 node39->node42 cost [USD] node44 node39->node44 mass [g] node40 solar_panels node43 node40->node43 cost [USD] node45 node40->node45 mass [g] node4 node40->node4 node5 node40->node5 node41->node40 power_profile [W] node42->node37 [USD] node43->node37 [USD] node44->node38 [g] node45->node38 [g] node46 0 g node6 node46->node6 node52 area [m²] node4->node52 node48 solar_radiation [lx] node5->node48 node51 plutonium [g] node6->node51 node7 node7->node39 node13 thermocouple node7->node13 node27 solar_panels node7->node27 node29 battery node7->node29 node9 0 m² node9->node4 node10 0 lx node10->node5 node12 node12->node2 node22 node13->node22 heat [W] node23 node13->node23 mass [g] node13->node3 node15 × 0.00179 kg/W node21 node15->node21 [kg] node16 node24 node16->node24 mass [g] node17 node19 node17->node19 [kg] node20 node17->node20 [kg] node18 node18->node6 node19->node16 [kg] node20->node18 [kg] node21->node17 [kg] node22->node15 heat [W] node23->node12 [g] node24->node12 [g] node49 cost [USD] node3->node49 node26 0 g node26->node6 node27->node2 node27->node4 node27->node5 node27->node3 node32 0 m² node32->node4 node33 0 W node34 node34->node33 [W] node29->node2 node29->node3 node29->node34 charging_profile [W] node30 0 g node30->node6 node31 0 lx node31->node5 node47 power_profile [W] node47->node7

3 - Reference

3.1 - Describing Posets

All values belong to posets.

PyMCDP knows a few built-in posets, and gives you the possibility of creating your own.

3.1.A - Natural numbers

The natural numbers with a completion are expressed as Nat and their values using the syntax Nat:42.

3.1.B - Floating point numbers

Floating point with completion are indicated by R, and their values as 42 [].

Floating point with completion and units are indicated using units, such as:

g, J, m, s, m/s, …

Their values are indicated as follows:

10 g, 20 J, 10 m, 10 s, 23 m/s, …

3.1.C - Finite Posets

It is possible to define and use your own arbitrary finite posets.

For example, create a file named my_poset.mcdp_poset containing the following definition:

my_poset.mcdp_posetfinite_poset {
    a <= b <= c
    c <= d
    c <= e  
}

This defines a poset with 5 elements a, b, c, d, e and with the given order relations.

Now that this poset has been defined, it can be used in the definition of an MCDP, by referring to it by name using the backtick notation, as in “`my_poset”.

To refer to its elements, use the notation `my_poset: element.

For example:

mcdp {
    provides f [`my_poset]

    f <= `my_poset : c
}
G cluster1 node2 'c' node3 f [`my_poset] node3->node2

3.1.D - Poset Products

Use the Unicode symbol “×” or the simple letter x to create a poset product.

The syntax is

⟨space⟩ × ⟨space⟩ ×× ⟨space⟩

For example:

J × A

This represents a product of Joules and Amperes.

3.1.E - Tuple making

To create a tuple, use angular brackets.

The syntax is:

<⟨element⟩, ⟨element⟩,, ⟨element⟩>

An example using regular brackets:

<0 J, 1 A>

An example using fancy unicode brackets:

0 J, 1 A

3.1.F - Tuple accessing

To access the elements of the tuple, use the syntax

take(⟨value⟩, ⟨index⟩)

For example:

mcdp {
    provides out [ J x A ]

    take(provided out, 0) <= 10 J
    take(provided out, 1) <= 2 A
}

This is equivalent to

mcdp {
    provides out [ J x A ]

    provided out <= <10 J, 2 A>
}

3.1.G - Named Poset Products

PyMCDP also supports named products, in which each entry in the tuple is associated to a name. For example, the following declares a product of the two spaces J and A with the two entries named energy and current.

product(energy:J, current:A)

Then it is possible to index those entries using one of these two syntaxes:

take(⟨resource⟩, ⟨label⟩)
take(⟨functionality⟩, ⟨label⟩)
(⟨resource⟩).⟨label⟩
(⟨resource⟩).⟨label⟩

For example:

mcdp {
    provides out [ product(energy:J, current:A) ]

    (provided out).energy <= 10 J
    (provided out).current <= 2 A
}

3.2 - Operations on MCDPs

3.2.A - Abstraction and flattening

It is easy to create recursive composition in MCDP.

Composition1.mcdpmcdp {
  a = instance mcdp {
    c = instance mcdp {
      provides f [Nat]
      requires r [Nat]
      provided f + Nat:1 <= required r
    }

    provides f using c
    requires r for   c
  }

  b = instance mcdp {
    d = instance mcdp {
      provides f [Nat]
      requires r [Nat]
      provided f + Nat:1 <= required r
    }

    provides f using d
    requires r for   d
  }

  r required by a <= f provided by b
  requires r for b
  provides f using a
}
G cluster1 cluster2 a cluster3 c cluster5 b cluster6 d node8 node7 + 1 node8->node7 f [N] node4 + 1 node4->node8 r [N] node10 r [N] node7->node10 node9 f [N] node9->node4

We can completely abstract an MCDP, using the abstract keyword.

abstract ⟨mcdp⟩

For example:

abstract `Composition1
G node1 node3 r [N] node1->node3 node2 f [N] node2->node1

And we can also completely flatten it, by erasing the border between subproblems:

flatten `Composition1
G cluster1 node2 + 1 node4 node2->node4 [N] node3 + 1 node6 r [N] node3->node6 node4->node3 [N] node5 f [N] node5->node2

4 - Draft documentation

4.1 - Load

load <name>

`<name>

4.2 - Space expressions

4.2.A - set-of

(V)
set-of(V)

4.2.B - UpperSets

The syntax is

UpperSets(⟨poset⟩)

For example:

UpperSets(V)

4.2.C - Interval

The syntax is

Interval(⟨lower bound⟩, ⟨upper bound⟩)

For example:

Interval(1g, 10g)

4.2.D - Singletons

S(tag)

S(tag):*

4.3 - Constant expressions

4.3.A - Top, Bottom

The syntax is:

Top ⟨space⟩
 ⟨space⟩
Bottom ⟨space⟩
  ⟨space⟩

For example:

Top V
 V
Bottom V
 V

4.3.B - set making

The syntax is:

{⟨element⟩, ⟨element⟩,, ⟨element⟩}

For example:

{0 g, 1 g}

To create an empty set:

EmptySet ⟨space⟩

4.3.C - upperclosure

The syntax is:

upperclosure ⟨set⟩

For example:

upperclosure {0 g, 1 g}

4.4 - Operations

4.4.A - ignore

Suppose f has type F. Then:

ignore ⟨functionality⟩ provided by ⟨dp⟩

is equivalent to

⟨functionality⟩ provided by ⟨dp⟩ >= any-of(Minimals ⟨space⟩)

Equivalently,

ignore ⟨resource⟩ required by ⟨dp⟩

is equivalent to

⟨resource⟩ required by ⟨dp⟩ <= any-of(Maximals ⟨space⟩)

4.4.B - available math operators

ceil
sqrt
square
pow
max
min

4.5 - Operations on NDPs

provides ⟨functionality⟩ using ⟨dp⟩
requires ⟨resource⟩ for ⟨dp⟩

4.5.A - implemented-by

4.5.B - approx

approx()

4.5.C - abstract

abstract ⟨mcdp⟩

4.5.D - compact

The command compact takes an MCDP and produces another with “compacted” edges:

compact ⟨mcdp⟩

For every pair of DPS that have more than one edge between them, those edges are being replaced.

mcdp {
      a = instance template mcdp {
          provides f [Nat]
          requires r1 [Nat]
          requires r2 [Nat]
      }
      b = instance template mcdp {
          provides f1 [Nat]
          provides f2 [Nat]
          requires r [Nat]
      }
      a.r1 <= b.f1
      a.r2 <= b.f2
    }

Original:

G cluster1 node2 a node4 node2->node4 r1 [N] node5 node2->node5 r2 [N] node3 b node7 node3->node7 r [N] node4->node3 f1 [N] node5->node3 f2 [N] node6 node6->node2 f [N]

Compacted:

G cluster1 node2 a node4 node2->node4 r1_r2 [((N×N)×)] node3 b node6 node3->node6 r [N] node4->node3 r1_r2 [((N×N)×)] node5 node5->node2 f [N]

4.5.E - template

template ⟨mcdp⟩

4.5.F - flatten

flatten ⟨mcdp⟩

4.5.G - canonical

This puts the MCDP in a canonical form:

canonical ⟨mcdp⟩

4.5.H - approx_lower, approx_upper

This creates a lower and upper bound for the MCDP:

approx_lower(⟨n⟩, ⟨mcdp⟩)
approx_upper(⟨n⟩, ⟨mcdp⟩)
### solve
solve(⟨functionality⟩, ⟨mcdp⟩)
### Uncertain TODO ### Asserts
assert_equal
assert_leq
assert_geq
assert_lt
assert_gt
assert_empty
assert_nonempty